using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Collections.Specialized;
using System.Collections.Generic;

namespace ReactiveCode.Linq
{
    using Observables;
    using Util;

    partial class ObservableEnumerable
    {
        internal static IObservableEnumerable<T> OnlyMaterializedItems<T>(this IObservableEnumerable<T> source)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");

            return Create<T>(o =>
            {
                var items = new List<Tuple<IObservable<T>, LatestValue<T>>>();
                Func<IObservable<T>, int> indexFor = item => items.TakeWhile(t => t.Item1 != item).Count(t => t.Item2.HasValue);
                var subscription = source.SubscribeWithClear(
                    onAdded: (item, index) =>
                    {
                        if (index < 0 || index > items.Count)
                            index = items.Count;

                        var value = item.LatestContinuous();
                        var tuple = Tuple.Create(item, value);
                        items.Insert(index, tuple);
                        if (value.HasValue)
                            o.OnAdded(value, indexFor(item));
                        else
                        {
                            IDisposable itemSubscription = null;
                            itemSubscription = value.Subscribe(x =>
                            {
                                Contract.Assert(itemSubscription != null);
                                o.OnAdded(value, indexFor(item));
                                itemSubscription.Dispose();
                            });
                        }
                    },
                    onRemoved: (item, index) =>
                    {
                        if (index < 0 || index >= items.Count || items[index].Item1 != item)
                        {
                            index = items.IndexOf(t => t.Item1 == item);
                            if (index < 0)
                                return;
                        }
                        var tuple = items[index];
                        if (tuple.Item2.HasValue)
                            o.OnRemoved(item, indexFor(item));
                        tuple.Item2.Dispose();
                    },
                    onCleared: delegate
                    {
                        foreach(var tuple in items)
                            tuple.Item2.Dispose();
                        items.Clear();
                        o.OnCleared();
                    },
                    onError: o.OnError,
                    onCompleted: o.OnCompleted
                );
                return delegate
                {
                    foreach(var tuple in items)
                        tuple.Item2.Dispose();
                    items.Clear();
                    subscription.Dispose();
                };
            });
        }

        public static ReadOnlyObservableCollection<T> ToObservableCollection<T>(this IObservableEnumerable<T> source)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            IDisposable subscription;
            return source.ToObservableCollection(out subscription);
        }
        public static ReadOnlyObservableCollection<T> ToObservableCollection<T>(this IObservableEnumerable<T> source, out IDisposable subscription)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");

            var collection = new ObservableCollection<T>();
            var itemSubscriptions = new List<IDisposable>();
            var sourceSubscription = source.OnlyMaterializedItems().SubscribeWithClear(
                onAdded: (item, i) =>
                {
                    Contract.Assert(i >= 0 && i <= collection.Count);
                    collection.Insert(i, item.Last());
                    itemSubscriptions.Insert(i, item.Subscribe(x => collection[i] = x));
                },
                onRemoved: (item, i) =>
                {
                    Contract.Assert(i >= 0 && i <= collection.Count);
                    Contract.Assert(Equals(collection[i], item.Latest()));
                    itemSubscriptions[i].Dispose();
                    itemSubscriptions.RemoveAt(i);
                    collection.RemoveAt(i);
                },
                onCleared: delegate
                {
                    foreach(var sbscr in itemSubscriptions)
                        sbscr.Dispose();
                    itemSubscriptions.Clear();
                    collection.Clear();
                }
            );
            subscription = Disposable.Create(delegate
            {
                sourceSubscription.Dispose();
                foreach (var sbscr in itemSubscriptions)
                    sbscr.Dispose();
            });
            return new ReadOnlyObservableCollection<T>(collection);
        }
    }
}